package com.okex.demo.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.internal.LinkedTreeMap;
import com.okex.demo.model.PositionInfo;
import com.okex.demo.model.TickerInfo;
import com.okex.open.api.bean.account.param.SetLeverage;
import com.okex.open.api.bean.trade.param.ClosePositions;
import com.okex.open.api.bean.trade.param.CancelOrder;
import com.okex.open.api.bean.trade.param.PlaceOrder;
import com.okex.open.api.service.account.AccountAPIService;
import com.okex.open.api.service.marketData.MarketDataAPIService;
import com.okex.open.api.service.publicData.PublicDataAPIService;
import com.okex.open.api.service.trade.TradeAPIService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.InitializingBean;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import java.util.Date;

/**
 * OKX API 服务类
 * 提供 OKX 交易平台常用 API 调用的封装，包括行情查询、持仓信息、交易操作等
 */
@Slf4j
@Service
public class OkexApiService implements InitializingBean {

    @Autowired
    private AccountAPIService accountAPIService;

    @Autowired
    private MarketDataAPIService marketDataAPIService;

    @Autowired
    private PublicDataAPIService publicDataAPIService;

    @Autowired
    private TradeAPIService tradeAPIService;

    @Value("${okex.default.inst-id:BTC-USDT}")
    private String defaultInstId;

    /**
     * 服务初始化后自动检查本地时间与OKX服务器时间的同步状态
     * OKX API 要求客户端时间与服务器时间的差值不能超过30秒
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        syncTimeCheck();
    }

    /**
     * 检查本地时间与服务器时间的差异
     * OKX API 对时间同步有严格要求，时间差异超过30秒会导致API调用失败
     * 通过获取服务器时间并与本地时间比较，提前发现可能导致API调用失败的时间同步问题
     */
    private void syncTimeCheck() {
        try {
            // 获取服务器时间
            JSONObject systemTime = publicDataAPIService.getSystemTime();

            if (systemTime != null) {
                ArrayList data = (ArrayList) systemTime.get("data");
                if (data != null && !data.isEmpty()) {
                    LinkedTreeMap o = (LinkedTreeMap) data.get(0);
                    // 解析服务器时间戳
                    long serverTimestamp = Long.parseLong(o.get("ts").toString());
                    // 获取本地时间戳
                    long localTimestamp = System.currentTimeMillis();
                    // 计算差异（毫秒）
                    long diff = Math.abs(localTimestamp - serverTimestamp);

                    log.info("时间同步检查 - 本地时间: {}, 服务器时间: {}, 差异: {} 毫秒",
                            formatTimestamp(localTimestamp),
                            formatTimestamp(serverTimestamp),
                            diff);
                    // 如果差异大于30秒，发出警告
                    if (diff > 30000) {
                        log.warn("警告：本地时间与服务器时间差异过大 ({}毫秒)，可能导致API请求失败！请同步系统时间。", diff);
                    }
                } else {
                    log.warn("获取服务器时间响应格式异常，无法进行时间同步检查");
                }
            } else {
                log.error("无法获取服务器时间，请检查网络连接");
            }
        } catch (Exception e) {
            log.error("检查时间同步时出错: {}", e.getMessage(), e);
        }
    }

    /**
     * 格式化时间戳为可读时间字符串
     *
     * @param timestamp Unix时间戳（毫秒）
     * @return 格式化后的日期时间字符串，格式：yyyy-MM-dd HH:mm:ss.SSS
     */
    private String formatTimestamp(long timestamp) {
        return Instant.ofEpochMilli(timestamp)
                .atZone(ZoneId.systemDefault())
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
    }

    /**
     * 获取用户全部持仓信息
     * 获取当前账户下的所有持仓信息，包括多空方向、杠杆倍数、持仓数量、浮动盈亏等数据
     *
     * @return 持仓信息列表，如果没有持仓或发生错误则返回空列表
     */
    public List<PositionInfo> getPositions() {
        try {
            // 调用账户API获取全部持仓信息
            JSONObject result = accountAPIService.getPositions(null, null, null);
            if (result != null && result.getInteger("code") == 0) {
                JSONArray data = result.getJSONArray("data");
                if (data != null && !data.isEmpty()) {
                    List<PositionInfo> positions = new ArrayList<>(data.size());
                    for (int i = 0; i < data.size(); i++) {
                        JSONObject jsonObject = data.getJSONObject(i);
                        positions.add(PositionInfo.fromJsonObject(jsonObject));
                    }
                    log.debug("成功获取持仓信息: {} 条", positions.size());
                    return positions;
                } else {
                    log.debug("没有获取到持仓信息");
                    return Collections.emptyList();
                }
            } else {
                String errorCode = result != null ? String.valueOf(result.get("code")) : "unknown";
                String errorMsg = result != null ? String.valueOf(result.get("msg")) : "unknown";
                log.error("获取持仓信息失败: code={}, msg={}", errorCode, errorMsg);
            }
        } catch (Exception e) {
            log.error("获取持仓信息异常: {}", e.getMessage(), e);
        }
        return Collections.emptyList();
    }

    /**
     * 获取特定产品类型的所有行情信息
     *
     * @param instType 产品类型，必填，如:
     *                 SPOT: 币币
     *                 SWAP: 永续合约
     *                 FUTURES: 交割合约
     *                 OPTION: 期权
     * @param uly 标的指数，适用于交割/永续/期权，如 BTC-USD
     * @param instFamily 交易品种，适用于交割/永续/期权，如 BTC-USD
     * @return 产品行情信息列表，如果发生错误则返回空列表
     * @throws IllegalArgumentException 当instType参数为空时抛出
     */
    public List<TickerInfo> getTickersList(String instType, String uly, String instFamily) {
        try {
            if (instType == null || instType.isEmpty()) {
                throw new IllegalArgumentException("产品类型(instType)不能为空");
            }

            log.debug("获取{}类型行情数据, uly={}, instFamily={}", instType, uly, instFamily);
            JSONObject result = marketDataAPIService.getTickers(instType, instFamily, uly);
            if (result != null && result.getInteger("code") == 0) {
                JSONArray data = result.getJSONArray("data");
                if (data != null && !data.isEmpty()) {
                    List<TickerInfo> tickers = new ArrayList<>(data.size());
                    for (int i = 0; i < data.size(); i++) {
                        JSONObject jsonObject = data.getJSONObject(i);
                        tickers.add(TickerInfo.fromJsonObject(jsonObject));
                    }
                    log.debug("成功获取{}类型行情数据: {} 条", instType, tickers.size());
                    return tickers;
                } else {
                    log.debug("{}类型没有可用的行情数据", instType);
                }
            } else {
                String errorCode = result != null ? String.valueOf(result.get("code")) : "unknown";
                String errorMsg = result != null ? String.valueOf(result.get("msg")) : "unknown";
                log.error("获取{}类型行情数据失败: code={}, msg={}", instType, errorCode, errorMsg);
            }
        } catch (IllegalArgumentException e) {
            log.error("获取行情数据参数错误: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("获取{}类型行情数据异常: {}", instType, e.getMessage(), e);
        }
        return Collections.emptyList();
    }

    /**
     * 获取单个产品的最新行情信息
     *
     * @param instId 产品ID，如 BTC-USDT，如为空则使用默认产品ID
     * @return 行情信息，如果产品不存在或发生错误则返回null
     */
    public TickerInfo getTicker(String instId) {
        try {
            if (instId == null || instId.isEmpty()) {
                instId = defaultInstId;
                log.debug("产品ID为空，使用默认产品ID: {}", defaultInstId);
            }

            log.debug("获取产品{}行情数据", instId);
            JSONObject result = marketDataAPIService.getTicker(instId);
            if (result != null) {
                // 检查API返回的错误码
                Integer code = result.getInteger("code");
                if (code != null && code == 0) {
                    JSONArray data = result.getJSONArray("data");
                    if (data != null && !data.isEmpty()) {
                        JSONObject jsonObject = data.getJSONObject(0);
                        TickerInfo tickerInfo = TickerInfo.fromJsonObject(jsonObject);

                        // 确保设置当前时间作为更新时间
                        Date currentTime = new Date();
                        tickerInfo.setUpdateTime(currentTime);
                        log.debug("成功获取产品{}行情数据，设置更新时间: {}", instId, currentTime);

                        return tickerInfo;
                    } else {
                        log.warn("产品{}没有返回行情数据", instId);
                    }
                } else {
                    // 产品ID不存在的特定错误码
                    if (code != null && code == 51001) {
                        log.warn("产品ID不存在: {}", instId);
                    } else {
                        String errorMsg = result.getString("msg");
                        log.error("获取产品{}行情数据失败: code={}, msg={}", instId, code, errorMsg);
                    }
                }
            } else {
                log.error("获取产品{}行情数据失败: API返回null", instId);
            }
        } catch (Exception e) {
            log.error("获取产品{}行情数据异常: {}", instId, e.getMessage(), e);
        }
        return null;
    }

    /**
     * 一键平仓操作
     * 以市价单的方式，对指定产品的仓位进行平仓操作
     *
     * @param instId 产品ID，必填，如 BTC-USDT
     * @param posSide 持仓方向，必填，可选值：
     *                long: 多仓
     *                short: 空仓
     * @param mgnMode 保证金模式，必填，可选值：
     *                cross: 全仓
     *                isolated: 逐仓
     * @return 平仓结果JSON，包含ordId等信息，失败时包含错误码和错误信息
     */
    public JSONObject closePosition(String instId, String posSide, String mgnMode) {
        try {
            log.info("执行一键平仓: instId={}, posSide={}, mgnMode={}", instId, posSide, mgnMode);

            // 构建平仓参数
            ClosePositions closePositions = new ClosePositions();
            closePositions.setInstId(instId);
            closePositions.setPosSide(posSide);
            closePositions.setMgnMode(mgnMode);
            closePositions.setAutoCxl("false");  // 不自动撤单

            // 添加客户端订单ID，便于跟踪
            String clOrdId = new Date().getTime()+"";
            closePositions.setClOrdId(clOrdId);

            // 调用交易API执行平仓操作
            JSONObject result = tradeAPIService.closePositions(closePositions);

            // 记录平仓结果
            if (result != null) {
                Integer code = result.getInteger("code");
                if (code != null && code == 0) {
                    log.info("一键平仓成功: instId={}, clOrdId={}", instId, clOrdId);
                } else {
                    String errorMsg = result.getString("msg");
                    log.warn("一键平仓失败: instId={}, clOrdId={}, code={}, msg={}",
                            instId, clOrdId, code, errorMsg);
                }
                return result;
            } else {
                log.error("一键平仓API返回null: instId={}, clOrdId={}", instId, clOrdId);
                JSONObject errorResult = new JSONObject();
                errorResult.put("code", -1);
                errorResult.put("msg", "平仓API返回null");
                return errorResult;
            }
        } catch (Exception e) {
            log.error("一键平仓异常: instId={}, error={}", instId, e.getMessage(), e);
            JSONObject errorResult = new JSONObject();
            errorResult.put("code", -1);
            errorResult.put("msg", "平仓失败: " + e.getMessage());
            return errorResult;
        }
    }

    /**
     * 取消指定订单
     *
     * @param instId 产品ID，必填，如 BTC-USDT
     * @param ordId 订单ID，ordId和clOrdId必须传一个，若传两个，以ordId为主
     * @param clOrdId 客户自定义订单ID
     * @return 取消订单结果
     */
    public JSONObject cancelOrder(String instId, String ordId, String clOrdId) {
        try {
            if (instId == null || instId.isEmpty()) {
                throw new IllegalArgumentException("产品ID不能为空");
            }

            if ((ordId == null || ordId.isEmpty()) && (clOrdId == null || clOrdId.isEmpty())) {
                throw new IllegalArgumentException("订单ID和客户订单ID至少需要提供一个");
            }

            CancelOrder cancelOrder = new CancelOrder();
            cancelOrder.setInstId(instId);

            if (ordId != null && !ordId.isEmpty()) {
                cancelOrder.setOrdId(ordId);
            }

            if (clOrdId != null && !clOrdId.isEmpty()) {
                cancelOrder.setClOrdId(clOrdId);
            }

            log.info("取消订单: instId={}, ordId={}, clOrdId={}", instId, ordId, clOrdId);
            JSONObject result = tradeAPIService.cancelOrder(cancelOrder);

            if (result != null) {
                Integer code = result.getInteger("code");
                if (code != null && code == 0) {
                    log.info("取消订单成功: instId={}, ordId={}", instId, ordId);
                } else {
                    String errorMsg = result.getString("msg");
                    log.warn("取消订单失败: instId={}, ordId={}, code={}, msg={}",
                            instId, ordId, code, errorMsg);
                }
                return result;
            } else {
                log.error("取消订单API返回null: instId={}, ordId={}", instId, ordId);
                JSONObject errorResult = new JSONObject();
                errorResult.put("code", -1);
                errorResult.put("msg", "取消订单API返回null");
                return errorResult;
            }
        } catch (Exception e) {
            log.error("取消订单异常: instId={}, ordId={}, error={}", instId, ordId, e.getMessage(), e);
            JSONObject errorResult = new JSONObject();
            errorResult.put("code", -1);
            errorResult.put("msg", "取消订单失败: " + e.getMessage());
            return errorResult;
        }
    }

    /**
     * 获取产品深度（订单簿）
     *
     * @param instId 产品ID，如 BTC-USDT
     * @param size 返回深度数量，最大值为400，即买卖深度各400条
     * @return 深度数据JSON，包含bids（买单）和asks（卖单）
     */
    public JSONObject getOrderBook(String instId, String size) {
        try {
            if (instId == null || instId.isEmpty()) {
                instId = defaultInstId;
                log.debug("产品ID为空，使用默认产品ID: {}", defaultInstId);
            }

            // 默认深度为5
            if (size == null || size.isEmpty()) {
                size = "5";
            }

            log.debug("获取产品{}深度数据, size={}", instId, size);
            JSONObject result = marketDataAPIService.getOrderBook(instId, size);

            if (result != null && result.getInteger("code") == 0) {
                log.debug("成功获取产品{}深度数据", instId);
                return result;
            } else {
                String errorCode = result != null ? String.valueOf(result.get("code")) : "unknown";
                String errorMsg = result != null ? String.valueOf(result.get("msg")) : "unknown";
                log.error("获取产品{}深度数据失败: code={}, msg={}", instId, errorCode, errorMsg);
            }
        } catch (Exception e) {
            log.error("获取产品{}深度数据异常: {}", instId, e.getMessage(), e);
        }

        JSONObject errorResult = new JSONObject();
        errorResult.put("code", -1);
        errorResult.put("msg", "获取深度数据失败");
        return errorResult;
    }

    /**
     * 获取产品K线数据
     *
     * @param instId 产品ID，如 BTC-USDT
     * @param bar K线周期，如 1m/3m/5m/15m/30m/1H/2H/4H/6H/12H/1D/1W/1M/3M/6M/1Y
     * @param limit 返回的结果集数量，默认100，最大值为100
     * @return K线数据JSON
     */
    public JSONObject getCandlesticks(String instId, String bar, String limit) {
        try {
            if (instId == null || instId.isEmpty()) {
                instId = defaultInstId;
                log.debug("产品ID为空，使用默认产品ID: {}", defaultInstId);
            }

            // 默认K线周期为1分钟
            if (bar == null || bar.isEmpty()) {
                bar = "1m";
            }

            log.debug("获取产品{}K线数据, bar={}, limit={}", instId, bar, limit);
            JSONObject result = marketDataAPIService.getCandlesticks(instId, null, null, bar, limit);

            if (result != null && result.getInteger("code") == 0) {
                log.debug("成功获取产品{}K线数据", instId);
                return result;
            } else {
                String errorCode = result != null ? String.valueOf(result.get("code")) : "unknown";
                String errorMsg = result != null ? String.valueOf(result.get("msg")) : "unknown";
                log.error("获取产品{}K线数据失败: code={}, msg={}", instId, errorCode, errorMsg);
            }
        } catch (Exception e) {
            log.error("获取产品{}K线数据异常: {}", instId, e.getMessage(), e);
        }

        JSONObject errorResult = new JSONObject();
        errorResult.put("code", -1);
        errorResult.put("msg", "获取K线数据失败");
        return errorResult;
    }

    /**
     * 下单
     *
     * @param instId 产品ID，必填，如 BTC-USDT
     * @param tdMode 交易模式，必填，可选值：
     *               cross: 全仓
     *               isolated: 逐仓
     * @param side 订单方向，必填，可选值：
     *             buy: 买入
     *             sell: 卖出
     * @param posSide 持仓方向，必填，可选值：
     *                long: 多仓
     *                short: 空仓
     * @param sz 委托数量（张数），必填
     * @return 下单结果JSON，包含ordId等信息，失败时包含错误码和错误信息
     */
    public JSONObject placeOrder(String instId, String tdMode, String side, String posSide, String sz) {
        return placeOrder(instId, tdMode, side, posSide, sz, null);
    }

    /**
     * 下单（带杠杆倍数）
     *
     * @param instId 产品ID，必填，如 BTC-USDT
     * @param tdMode 交易模式，必填，可选值：
     *               cross: 全仓
     *               isolated: 逐仓
     * @param side 订单方向，必填，可选值：
     *             buy: 买入
     *             sell: 卖出
     * @param posSide 持仓方向，必填，可选值：
     *                long: 多仓
     *                short: 空仓
     * @param sz 委托数量（张数），必填
     * @param leverage 杠杆倍数，可选，如 20、50、100
     * @return 下单结果JSON，包含ordId等信息，失败时包含错误码和错误信息
     */
    public JSONObject placeOrder(String instId, String tdMode, String side, String posSide, String sz, String leverage) {
        try {
            log.info("执行下单: instId={}, tdMode={}, side={}, posSide={}, sz={}, leverage={}",
                    instId, tdMode, side, posSide, sz, leverage);

            // 构建下单参数
            PlaceOrder placeOrder = new PlaceOrder();
            placeOrder.setInstId(instId);
            placeOrder.setTdMode(tdMode);
            placeOrder.setSide(side);
            placeOrder.setPosSide(posSide);
            placeOrder.setOrdType("market"); // 市价单
            placeOrder.setSz(sz);

            // 设置杠杆倍数（如果提供）
            if (leverage != null && !leverage.isEmpty()) {
                try {
                    // 设置杠杆倍数
                    SetLeverage setLeverageParam = new SetLeverage();
                    setLeverageParam.setInstId(instId);
                    setLeverageParam.setLever(leverage);
                    setLeverageParam.setPosSide(posSide);
                    setLeverageParam.setMgnMode(tdMode);

                    JSONObject leverageResult = accountAPIService.setLeverage(setLeverageParam);
                    if (leverageResult != null && leverageResult.getInteger("code") == 0) {
                        log.info("设置杠杆倍数成功: instId={}, leverage={}", instId, leverage);
                    } else {
                        String errorCode = leverageResult != null ? String.valueOf(leverageResult.get("code")) : "unknown";
                        String errorMsg = leverageResult != null ? String.valueOf(leverageResult.get("msg")) : "unknown";
                        log.warn("设置杠杆倍数失败: instId={}, leverage={}, code={}, msg={}",
                                instId, leverage, errorCode, errorMsg);
                    }
                } catch (Exception e) {
                    log.warn("设置杠杆倍数异常: instId={}, leverage={}, error={}", instId, leverage, e.getMessage());
                }
            }

            // 添加客户端订单ID，便于跟踪
            String clOrdId = new Date().getTime()+"";
            placeOrder.setClOrdId(clOrdId);

            // 调用交易API执行下单操作
            JSONObject result = tradeAPIService.placeOrder(placeOrder);

            // 记录下单结果
            if (result != null) {
                Integer code = result.getInteger("code");
                if (code != null && code == 0) {
                    log.info("下单成功: instId={}, clOrdId={}", instId, clOrdId);
                } else {
                    String errorMsg = result.getString("msg");
                    log.warn("下单失败: instId={}, clOrdId={}, code={}, msg={}",
                            instId, clOrdId, code, errorMsg);
                }
                return result;
            } else {
                log.error("下单API返回null: instId={}, clOrdId={}", instId, clOrdId);
                JSONObject errorResult = new JSONObject();
                errorResult.put("code", -1);
                errorResult.put("msg", "下单API返回null");
                return errorResult;
            }
        } catch (Exception e) {
            log.error("下单异常: instId={}, error={}", instId, e.getMessage(), e);
            JSONObject errorResult = new JSONObject();
            errorResult.put("code", -1);
            errorResult.put("msg", "下单失败: " + e.getMessage());
            return errorResult;
        }
    }
}
